package main

import (
    "io/ioutil"
    "os/exec"
    "time"
    "fmt"
    "os"
    "bytes"
    "strings"
    "strconv"
    "text/template"
    "crypto/md5"
)

type FileData struct {
    Name        string `json:"name"`
    BeforeCmd   string `json:"before_cmd"`
    File        string `json:"file"`
    Data        string `json:"data"`
    AfterCmd    string `json:"after_cmd"`
    UpdateTime  string `json:"update_time"`
}

func saveDataToFile(fdata *FileData) error {
    if len(fdata.File) == 0 {
        return nil
    }

    //检查时间戳
    var remote_time int64 = 0
    if len(fdata.UpdateTime) > 0 {
        t, err := strconv.ParseInt(fdata.UpdateTime, 10, 64) 
        if err != nil {
            mainlog.Printf("update_time filed parse error. (%s) (%s)\n", err.Error(), fdata.UpdateTime)
        } else {
            remote_time = t
        }
    }

    var local_time int64 = 0
    fileinfo, err := os.Stat(fdata.File)
    if err != nil && !os.IsNotExist(err) {
        mainlog.Printf("get file stat error.(%s) (%s)\n", err.Error(), fdata.File)
    } else if err == nil {
        local_time = fileinfo.ModTime().Unix()
    }

    mainlog.Printf("mtime check, remote(%d) local(%d) file(%s)\n", remote_time, local_time, fdata.File)

    if remote_time == local_time && remote_time != 0 {
        mainlog.Printf("file timestamp not changed. (%s)\n", fdata.File)
        return nil
    }

    //文件内容替换成实际内容
    {
        sys := struct {
            Mem int64
            Disk []string
        }{
            Mem: getMem(),
            Disk: getDisk(),
        }

        fm := template.FuncMap{"divide": func(a, b int64) int64 {
            return a / b
        }}

        tmpt, err := template.New("").Funcs(fm).Parse(fdata.Data)
        if err == nil {
            var buf bytes.Buffer
            err = tmpt.Execute(&buf, &sys)
            if err == nil {
                fdata.Data = string(buf.Bytes())
            } else {
                mainlog.Printf("%s\n", err.Error())
            }
        } else {
            mainlog.Printf("%s\n", err.Error())
        }
    }

    //检查md5
    lb, err := ioutil.ReadFile(fdata.File)
    if err != nil && !os.IsNotExist(err) {
        mainlog.Printf("read file err. (%s) (%s)\n", err.Error(), fdata.File)
    } else {
        old_info := fmt.Sprintf("%x", md5.Sum(lb))
        new_info := fmt.Sprintf("%x", md5.Sum([]byte(fdata.Data)))

        mainlog.Printf("md5 check, remote(%s) local(%s) file(%s)\n", new_info, old_info, fdata.File)

        if old_info == new_info {
            mainlog.Printf("file md5 not changed. (%s)\n", fdata.File)
            return nil
        }
    }

    //写入文件
    file := fmt.Sprintf("%s-%d", fdata.File, time.Now().UnixNano())
    err = ioutil.WriteFile(file, []byte(fdata.Data), 0644)
    if err != nil {
        mainlog.Printf("write file failed. (%s) (%s)\n", err.Error(), fdata.File)
        return err
    }

    err = os.Rename(file, fdata.File)
    if err != nil {
        mainlog.Printf("rename file failed. (%s) (%s)\n", err.Error(), fdata.File)
        return err
    }

    if remote_time != 0 {
        if err := os.Chtimes(fdata.File, time.Unix(remote_time, 0), time.Unix(remote_time, 0)); err != nil {
            mainlog.Printf("change file modtime failed. (%s) (%s)\n", err.Error(), fdata.File)
            return err
        }
    }

    return nil
}

func dealWithFileData(fdata *FileData) error {
    if len(fdata.BeforeCmd) > 0 {
        cmd := exec.Command("sh", "-c", fdata.BeforeCmd)
        var out bytes.Buffer
        cmd.Stderr = &out
        err := cmd.Run()
        if err != nil {
            mainlog.Printf("run before cmd failed. (%s) (%s)\n", err.Error(), strings.TrimSpace(out.String()))
            return fmt.Errorf("run before_cmd failed. (%s) (%s)\n", err.Error(), strings.TrimSpace(out.String()))
        }
    }

    err := saveDataToFile(fdata)
    if err != nil {
        return fmt.Errorf("save file error. (%s)\n", err.Error())
    }

    if len(fdata.AfterCmd) > 0 {
        cmd := exec.Command("sh", "-c", fdata.AfterCmd)
        var out bytes.Buffer
        cmd.Stderr = &out
        err := cmd.Run()
        if err != nil {
            mainlog.Printf("run after cmd failed. (%s) (%s)", err.Error(), strings.TrimSpace(out.String()))
            return fmt.Errorf("run after_cmd failed. (%s) (%s)", err.Error(), strings.TrimSpace(out.String()))
        }
    }

    return nil
}

func getMem() int64 {
    content, err := ioutil.ReadFile("/proc/meminfo")
    if err != nil {
        mainlog.Printf("%s\n", err.Error())
        return 0
    }

    lines := strings.Split(string(content), "\n")
    for _, line := range lines {
        fileds := strings.Fields(line)
        if len(fileds) == 0 {
            continue
        }

        if fileds[0] == "MemTotal:" {
            m, _ := strconv.ParseInt(fileds[1], 10, 64)
            m *= 1024
            return m
        }
    }
    return 0
}

func getDisk() []string {
    ret := make([]string, 0)
    content, err := ioutil.ReadFile("/proc/diskstats")
    if err != nil {
        mainlog.Printf("%s\n", err.Error())
        return ret
    }

    lines := strings.Split(string(content), "\n")
    for _, line := range lines {
        fields := strings.Fields(line)
        if len(fields) < 3 {
            continue
        }

        if (strings.Index(fields[2], "vd") == 0 || strings.Index(fields[2], "sd") == 0) &&
            strings.Index(fields[2], "vda") != 0 && strings.Index(fields[2], "sda") != 0 {
                ret = append(ret, fields[2])
        }
    }

    return ret
}